package gateway;

import java.util.ArrayList;
import java.util.Random;

import com.CharNotValidException;
import com.Constants;
import com.Event;
import com.ParamsLabel;
import com.sun.spot.peripheral.Spot;
import com.sun.spot.util.IEEEAddress;

import debug.TimeRegister;

import gateway.behaviours.ACLMessageSenderBehaviour;
import gateway.behaviours.CommunicationChannelReceiverBehaviour;
import gateway.behaviours.CommunicationChannelSenderBehaviour;
import gateway.behaviours.HandleGatewayRequestBehaviour;
import gateway.ontology.GatewayOntology;
import jade.content.lang.Codec;
import jade.content.lang.sl.SLCodec;
import jade.content.onto.Ontology;
import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.ThreadedBehaviourFactory;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPANames;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.util.leap.Iterator;
import wrapper.SunSpotWrapper;

public class GatewayAgent extends Agent {

	private String communicationPort = "120";

	private IEEEAddress myIEEEAddress;
	private String executionEngineName;

	private Object lock = new Object();
	private ArrayList<AID> localJADEagents;
	private ArrayList<String> localMAPSagents;
	private MobileAgentNaming naming;

	private ACLMessageSenderBehaviour aclmessageSender;
	private CommunicationChannelSenderBehaviour communicationChannelSender;
	private CommunicationChannelReceiverBehaviour communicationChannelReceiver;

	private HandleGatewayRequestBehaviour requestHandler;

	private ThreadedBehaviourFactory tbf = new ThreadedBehaviourFactory();

	private Codec codec = new SLCodec();
	private Ontology ontology = GatewayOntology.getInstance();
	
	/*
	 * TODO DEBUG
	 */
	private TimeRegister timeRegister = new TimeRegister();

	@Override
	public void setup() {
		// Initialize SunSpot's system properties.
		SunSpotWrapper.initSunSpot();
		myIEEEAddress = new IEEEAddress(Spot.getInstance().getRadioPolicyManager().getIEEEAddress());
		executionEngineName = "GATEWAY";
		localJADEagents = new ArrayList<AID>();
		localMAPSagents = new ArrayList<String>();

		// startup communication channel sender
		communicationChannelSender = new CommunicationChannelSenderBehaviour(
				communicationPort, this);
		addBehaviour(tbf.wrap(communicationChannelSender));

		// startup communication channel receiver
		communicationChannelReceiver = new CommunicationChannelReceiverBehaviour(
				communicationPort, this);
		addBehaviour(tbf.wrap(communicationChannelReceiver));

		aclmessageSender = new ACLMessageSenderBehaviour(this);
		addBehaviour(tbf.wrap(aclmessageSender));

		getContentManager().registerLanguage(codec);
		getContentManager().registerOntology(ontology);

		// setup message template
		MessageTemplate mt = MessageTemplate
				.and(MessageTemplate.MatchLanguage(codec.getName()),
						MessageTemplate.and(
								MessageTemplate.MatchOntology(ontology
										.getName()),
								MessageTemplate.and(
										MessageTemplate
												.MatchProtocol(FIPANames.InteractionProtocol.FIPA_REQUEST),
										MessageTemplate
												.MatchPerformative(ACLMessage.REQUEST))));

		// setup handling gateway requests
		MessageTemplate gmt = MessageTemplate.and(mt,
				MessageTemplate.MatchOntology(ontology.getName()));
		this.requestHandler = new HandleGatewayRequestBehaviour(gmt, this);
		this.addBehaviour(requestHandler);

		// setup Naming Server
		this.naming = new MobileAgentNaming();

		// Publish in MAPS
		publishMySelf();

		// Register GatewayAgent
		registerService();

		printInfo();
	}

	/**
	 * Print some basic information about the Gateway agent.
	 */
	private void printInfo() {
		System.out.println("--------------JADE----------------");
		System.out.println("My local-name is " + getAID().getLocalName());
		System.out.println("My GUID is " + getAID().getName());
		System.out.println("My addresses are:");
		Iterator it = getAID().getAllAddresses();
		while (it.hasNext()) {
			System.out.println("- " + it.next());
		}
		System.out.println("--------------MAPS----------------");
		System.out.println("My IEEE address is "
				+ this.myIEEEAddress.asDottedHex() + ".");
	}

	/**
	 * Register the jade-maps gateway service in yellow pages
	 */
	private void registerService() {
		DFAgentDescription dfd = new DFAgentDescription();
		dfd.setName(this.getAID());
		ServiceDescription sd = new ServiceDescription();
		sd.setName(this.getLocalName() + "-MAPS-JADE-gateway");
		sd.setType("MAPS-JADE-gateway");
		sd.addProtocols(FIPANames.InteractionProtocol.FIPA_REQUEST);
		sd.addLanguages(codec.getName());
		sd.addOntologies(ontology.getName());
		dfd.addServices(sd);
		try {
			DFService.register(this, dfd);
		} catch (FIPAException fe) {
			fe.printStackTrace();
		}
	}

	@Override
	public void takeDown() {
		tbf.interrupt();

		// unregister
		try {
			DFService.deregister(this);
		} catch (FIPAException fe) {
			fe.printStackTrace();
		}
		timeRegister.save("result.csv");
		System.out
				.println("GatewayAgent " + getAID().getName() + "terminated.");
	}

	private void publishMySelf() {
		Event event = new Event(this.myIEEEAddress.asDottedHex(),
				Constants.BROADCAST, Event.DSC_PUBLISH, Event.NOW);
		communicationChannelSender.put(event);
	}

	/**
	 * Adds a JADE agent to the a of agents. Agent's suffix is generated
	 * randomly.
	 * 
	 * @param agentID
	 *            an instance of AID of the agent to be added.
	 * @return returns TRUE if an agent was successfully registered, otherwise
	 *         FALSE.
	 */
	public boolean addLocalAgent(AID agentID) {
		return this.addLocalAgent(this.generateAgentSuffix(), agentID);
	}

	/**
	 * Adds a JADE agent to a list of agents.
	 * 
	 * @param suffix
	 *            string of characters
	 * @param agentID
	 *            an instance of AID of the agent to be added.
	 * @return TRUE if agent was successfully registered, otherwise FALSE.
	 */
	public boolean addLocalAgent(String suffix, AID agentID) {
		try {

			if (suffix == null)
				suffix = this.generateAgentSuffix();
			String mapsID = this.generateAgentID(suffix);

			boolean result = true;
			synchronized (lock) {
				if (localJADEagents.indexOf(agentID) != -1
						|| localMAPSagents.indexOf(mapsID) != -1) {
					result = false;
				} else {
					this.localJADEagents.add(agentID);
					this.localMAPSagents.add(mapsID);
				}

				lock.notifyAll();
			}
			if (!result) {
				return false;
			}

			System.out.println("[Gateway] + " + agentID.getName() + " as "
					+ mapsID);
			Event refreshEvent = new Event(this.executionEngineName,
					Constants.BROADCAST, Event.DSC_REFRESH, Event.NOW);
			refreshEvent.setParam(ParamsLabel.AGT_NAME, mapsID);
			communicationChannelSender.put(refreshEvent);
		} catch (CharNotValidException e) {
			e.printStackTrace();
			return false;
		} catch (AgentIDException e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	public String generateAgentID(String agentSuffix) throws AgentIDException {
		if (agentSuffix.length() > 6) {
			throw new AgentIDException(
					"Suffix is too long. Max 6 characters allowed");
		}
		String ieee = this.myIEEEAddress.asDottedHex();
		String agentID = ieee.substring(10);
		agentID += agentSuffix;

		return agentID;
	}

	public ArrayList<String> getLocalMAPSagents() {
		ArrayList<String> copy = null;
		synchronized (lock) {
			copy = (ArrayList<String>) localMAPSagents.clone();
			lock.notifyAll();
		}
		return copy;
	}

	/**
	 * Generate random suffix.
	 * 
	 * @return a generated suffix.
	 */
	public String generateAgentSuffix() {
		Random randomGen = new Random();
		return "" + randomGen.nextInt(Constants.MAX_AGENT_ID);
	}

	/**
	 * Removes a JADE agent from a list of agents.
	 * 
	 * @param agentID
	 *            an instance of AID of the agent to be removed.
	 */
	public boolean removeAgent(AID agentAID) {
		boolean result = false;

		synchronized (lock) {
			int index = localJADEagents.indexOf(agentAID);
			if(index > -1 ) {
				localJADEagents.remove(index);
				localMAPSagents.remove(index);
				result = true;
			}
			lock.notifyAll();
		}
		return result;
	}

	/**
	 * Gets an IEEEAddress of the gateway in SunSpot WSN.
	 * 
	 * @return an IEEEAddress of the gateway.
	 */
	public IEEEAddress getIEEAddress() {
		return this.myIEEEAddress;
	}

	public AID getLocalJADEAgent(String mapsID) {
		AID agentID = null;

		synchronized (lock) {
			int index = this.localMAPSagents.indexOf(mapsID);
			if (index > -1) {
				agentID = localJADEagents.get(index);
			}
			lock.notifyAll();
		}

		return agentID;
	}

	public String getLocalMAPSAgent(AID agentID) {
		String mapsID = null;

		synchronized (lock) {
			int index = this.localJADEagents.indexOf(agentID);
			if (index > -1) {
				mapsID = localMAPSagents.get(index);
			}
			lock.notifyAll();
		}

		return mapsID;
	}
	
	public ThreadedBehaviourFactory getTBF() {
		return tbf;
	}

	public MobileAgentNaming getNaming() {
		return naming;
	}

	public CommunicationChannelReceiverBehaviour getChannelReceiver() {
		return communicationChannelReceiver;
	}

	public CommunicationChannelSenderBehaviour getChannelSender() {
		return communicationChannelSender;
	}

	public ACLMessageSenderBehaviour getACLMessageSender() {
		return aclmessageSender;
	}
	
	/*
	 * TODO DEBUG
	 */
	public TimeRegister getTimeRegister() {
		return timeRegister;
	}
}
